package com.gengzp.system.utils;

import com.fasterxml.jackson.core.type.TypeReference;
import com.gengzp.common.config.ThreadPoolConfig;
import com.gengzp.common.context.MyBatisContextHolder;
import com.gengzp.common.enums.TaskExecuteStatusEnum;
import com.gengzp.common.enums.YesNoEnum;
import com.gengzp.common.model.generic.resp.Result;
import com.gengzp.common.model.task.entity.ScheduledTask;
import com.gengzp.common.utils.HttpRequestUtils;
import com.gengzp.common.utils.JsonUtils;
import com.gengzp.common.utils.RedisUtils;
import com.gengzp.rpc.utils.RpcUtils;
import com.gengzp.system.mapper.ScheduledTaskMapper;
import com.google.common.collect.Maps;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.DependsOn;
import org.springframework.scheduling.support.CronExpression;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;

import static com.gengzp.common.constants.HttpStatusCodeConstants.SUCCESS;
import static com.gengzp.common.constants.ScheduledTaskCacheConstants.SCHEDULED_TASK_EXECUTING_LOCK_PREFIX;

/**
 * @ClassName ScheduledTaskUtils
 * @Description 定时任务工具类
 * 优化点：历史错过的任务仅补执行一次，下次执行时间从当前时间重新计算
 * @Author gengzp
 * @Date 2025/10/10 17:48
 */
@Component
@DependsOn("redisUtils")
public class ScheduledTaskUtils {

    private static final Logger logger = LoggerFactory.getLogger(ScheduledTaskUtils.class);

    private static ScheduledTaskMapper scheduledTaskMapper;

    /**
     * 定时任务锁
     */
    private static final Object SCHEDULED_TASK_LOCK = new Object();

    /**
     * 最小查询间隔（毫秒）：避免短时间内高频查询数据库
     */
    private static final long MIN_QUERY_INTERVAL = 5000L;

    /**
     * 系统定时任务执行线程
     */
    private static final Thread SCHEDULED_TASK_EXECUTOR = new Thread(() -> {
        // 初始执行先休眠一段时间
        try {
            Thread.sleep(10000L);
        } catch (Exception ignored) {
        }

        while (true) {
            try {
                // 查询任务, 该方法会对任务进行执行时间从早到晚排序
                List<ScheduledTask> scheduledTasks = scheduledTaskMapper.selectNeedExecuteScheduledTask();
                if (CollectionUtils.isEmpty(scheduledTasks)) {
                    // 没有任务, 等待最小间隔后再次检查（可被唤醒）
                    synchronized (SCHEDULED_TASK_LOCK) {
                        SCHEDULED_TASK_LOCK.wait(MIN_QUERY_INTERVAL);
                    }
                    continue;
                }

                long waitTime = 0;
                Date currentDate = new Date();
                boolean hasCompensateTasks = false; // 是否有补执行任务

                for (ScheduledTask task : scheduledTasks) {
                    String lockId = UUID.randomUUID().toString().replace("-", "");
                    // 计算最小等待时间
                    waitTime = Math.max(0, task.getNextExecuteTime().getTime() - currentDate.getTime());

                    // 有错过的任务则立即执行（仅补执行一次）
                    if (!task.getNextExecuteTime().after(currentDate)) {
                        hasCompensateTasks = true; // 标记存在补执行任务
                        boolean isHistoryCompensate = true;
                        ThreadPoolConfig.getScheduledTaskThreadPool().execute(() ->
                        {
                            MyBatisContextHolder.setPrintSqlLogFlag(false);
                            String lockKey = SCHEDULED_TASK_EXECUTING_LOCK_PREFIX + task.getId();
                            try {
                                if (RedisUtils.setIfAbsent(lockKey, lockId, 10 * 60L)) {
                                    executeScheduledTask(task, isHistoryCompensate);
                                }
                            } catch (Exception e) {
                                logger.error("定时任务[{}]补执行异常", task.getId(), e);
                            } finally {
                                if (StringUtils.equals(RedisUtils.get(lockKey), lockId)) {
                                    RedisUtils.del(lockKey);
                                }
                            }
                        });
                    } else {
                        // 需要等待一段时间再执行的任务, 则无需继续执行
                        break;
                    }
                }

                // 若存在补执行任务，等待一段时间让异步任务更新数据库（避免查询旧数据）
                if (hasCompensateTasks) {
                    Thread.sleep(MIN_QUERY_INTERVAL);
                }

                // 休眠等待：优先使用计算的waitTime，若为0则使用最小间隔
                long sleepTime = waitTime > 0 ? waitTime : MIN_QUERY_INTERVAL;
                synchronized (SCHEDULED_TASK_LOCK) {
                    SCHEDULED_TASK_LOCK.wait(sleepTime);
                }

            } catch (Exception e) {
                logger.error("定时任务执行异常", e);
                try {
                    // 异常时休眠更长时间，避免频繁报错+查询
                    Thread.sleep(10000L);
                } catch (Exception ignored) {
                }
            }
        }
    }, "scheduled-task-executor");

    public ScheduledTaskUtils(ScheduledTaskMapper scheduledTaskMapper) {
        ScheduledTaskUtils.scheduledTaskMapper = scheduledTaskMapper;
    }

    /**
     * 执行定时任务（新增补执行标记参数）
     *
     * @param scheduledTask       定时任务
     * @param isHistoryCompensate 是否是历史任务补执行
     */
    private static void executeScheduledTask(ScheduledTask scheduledTask, boolean isHistoryCompensate) {
        // 为避免任务执行过程中修改了任务, 这里再次查询数据库中的任务数据
        scheduledTask = scheduledTaskMapper.selectScheduledTaskById(scheduledTask.getId());
        if (Objects.isNull(scheduledTask) || Objects.equals(scheduledTask.getStartStatus(), YesNoEnum.NO.getCode())) {
            logger.info("定时任务[{}]已禁用或不存在，跳过执行", scheduledTask.getId());
            return;
        }

        // 记录本次执行时间
        scheduledTask.setLastExecuteTime(new Date());
        LocalDateTime currentLdt = LocalDateTime.now();
        CronExpression cronExpression = CronExpression.parse(scheduledTask.getCronExpression());

        // 核心优化：计算下次执行时间
        if (isHistoryCompensate) {
            // 历史补执行：下次执行时间 = 当前时间 + 下一个cron周期（跳过所有历史堆积）
            LocalDateTime nextLdt = cronExpression.next(currentLdt);
            logger.info("定时任务[{}]历史补执行完成，下次执行时间从当前时间重新计算：{}",
                    scheduledTask.getId(), nextLdt);
            scheduledTask.setNextExecuteTime(Date.from(nextLdt.atZone(ZoneId.systemDefault()).toInstant()));
        } else {
            // 正常周期执行：下次执行时间 = 基于本次执行时间计算（原有逻辑）
            LocalDateTime nextLdt = cronExpression.next(currentLdt);
            scheduledTask.setNextExecuteTime(Date.from(nextLdt.atZone(ZoneId.systemDefault()).toInstant()));
        }

        Result<?> result = null;
        try {
            // 组装完整的定时任务接口请求url
            String address = String.format("http://%s%s",
                    RpcUtils.getAddressByServiceName(scheduledTask.getServiceName()),
                    scheduledTask.getUri());

            // 为其组装一个traceId
            Map<String, String> header = Maps.newHashMap();
            header.put("traceId", UUID.randomUUID().toString().replace("-", ""));

            // 调用定时任务接口
            result = HttpRequestUtils.post(address, null, header, new TypeReference<>() {
            });
        } catch (Exception e) {
            logger.error("定时任务[{}]执行接口调用异常", scheduledTask.getId(), e);
            result = Result.getFailResult(e.getMessage());
        }
        if (Objects.isNull(result)) {
            result = Result.getFailResult("未知错误导致定时任务执行失败");
        }

        // 更新执行状态和响应信息
        if (Objects.equals(result.getCode(), SUCCESS)) {
            scheduledTask.setLastExecuteStatus(TaskExecuteStatusEnum.EXECUTE_SUCCESS.getCode());
        } else {
            scheduledTask.setLastExecuteStatus(TaskExecuteStatusEnum.EXECUTE_FAILED.getCode());
        }
        scheduledTask.setLastExecuteResponse(JsonUtils.toJsonString(result));

        // 更新数据库中的定时任务执行信息
        scheduledTaskMapper.updateScheduledTaskAfterExecute(scheduledTask);
        logger.info("定时任务[{}]执行完成，状态：{}，下次执行时间：{}",
                scheduledTask.getTaskName(),
                Objects.equals(scheduledTask.getLastExecuteStatus(), TaskExecuteStatusEnum.EXECUTE_SUCCESS.getCode()) ? "成功" : "失败",
                scheduledTask.getNextExecuteTime());
    }

    /**
     * 启动系统定时任务执行器
     */
    public static void startScheduledTaskExecutor() {
        if (!SCHEDULED_TASK_EXECUTOR.isAlive()) {
            SCHEDULED_TASK_EXECUTOR.start();
            logger.info("定时任务执行器启动成功");
        } else {
            logger.info("定时任务执行器已处于运行状态");
        }
    }

    /**
     * 唤醒定时任务执行器
     */
    public static void notifyScheduledTaskExecutor() {
        synchronized (SCHEDULED_TASK_LOCK) {
            SCHEDULED_TASK_LOCK.notifyAll();
            logger.info("定时任务执行器已唤醒");
        }
    }

}